** Introduction Web Scraping ** Web scraping is a technique for converting the data present in unstructured format (HTML tags) over the web to the structured format which can easily be accessed and used. Most of the data available over the web is not readily available. It is present in an unstructured format (HTML format) and is not downloadable. Therefore, it requires knowledge and expertise to use this data.

We can locate useful data based on their CSS selectors, especially when the webpage uses semantic tag attributes. We can use [selectorgadget] (http://selectorgadget.com/) to find out which css selector matches the “review”. SelectGadget can be added an extension in Google chrome. It is shown as a magnifying glass.

pacman::p_load(tidyverse,tidytext,viridis,rvest,tm,wordcloud,SnowballC,tidyquant)

We can specify the css selector in html_nodes() and extract the text with html_text(). We scrab over 1800 reviews of Fiat Chrysler Automobiles from glassdoor. There are about 155 webpages which contain these reviews.

n=155
#The reviews has 155 pages,thus n=155
FCA_urls <- paste0("https://www.glassdoor.com/Reviews/FCA-Fiat-Chrysler-Automobiles-Reviews-E149_P",seq(2, n), ".htm")
FCA_urls<-c("https://www.glassdoor.com/Reviews/FCA-Fiat-Chrysler-Automobiles-Reviews-E149.htm",FCA_urls)
FCA_html <- FCA_urls %>%
    map_chr(~ read_html(.) %>% html_node(".hreview")%>%html_text())
FCA_html[[1]]
[1] "Featured Review Helpful (1)\"love the company has taught me a lot of new information in my contained growth as a mechanic\"StarStarStarStarStarCurrent Employee - Service Technician in Dayton, OHCurrent Employee - Service Technician in Dayton, OHI have been working at FCA Fiat Chrysler Automobiles full-time (More than 3 years)Prospros are that the company is great, pays for your training that will help in advancement. I get to work on the new cars and technology in the automotive fieldConsno cons as of yet, I love the work I do.and the folk I work with. plan on staying with the company through retirementAdvice to Managementkeep up the good workShare on FacebookShare on TwitterShare on WhatsAppShare via EmailCopy LinkLink Copied!Flag as InappropriateFlag as InappropriateHelpful (1) FCA Fiat Chrysler Automobiles Response seconds ago Edit •  Delete FCA Fiat Chrysler Automobiles 2017-09-30 21:14 PDT"

Data Preparation

We can remove all unwanted characters at this stage

#Data-Preprocessing: removing '\n'
FCA_html<-gsub("\n","",FCA_html)
#remove all round brackets
FCA_html<-FCA_html%>%str_replace_all("\\(|\\)", "")
#remove all \\
FCA_html<-FCA_html%>%str_replace_all("\\\\", "")
#remove all non words and non numbers
#FCAindeed2<-FCAindeed2%>%str_replace_all("[^A-Za-z0-9]", "")
#remove all • 
FCA_html<-FCA_html%>%str_replace_all("\\•  ", "")
#remove all & 
FCA_html<-FCA_html%>%str_replace_all("\\ & ", "")
#remove all  non printable words
FCA_html<-FCA_html%>%str_replace_all("[^[:print:]]", "")
FCA_html<-FCA_html%>%gsub(pattern = "\\ /", replacement = "")
#FCAindeed2<-FCAindeed2%>%stringi::stri_unescape_unicode()
FCA_html[[1]]
[1] "Featured Review Helpful 1\"love the company has taught me a lot of new information in my contained growth as a mechanic\"StarStarStarStarStarCurrent Employee - Service Technician in Dayton, OHCurrent Employee - Service Technician in Dayton, OHI have been working at FCA Fiat Chrysler Automobiles full-time More than 3 yearsProspros are that the company is great, pays for your training that will help in advancement. I get to work on the new cars and technology in the automotive fieldConsno cons as of yet, I love the work I do.and the folk I work with. plan on staying with the company through retirementAdvice to Managementkeep up the good workShare on FacebookShare on TwitterShare on WhatsAppShare via EmailCopy LinkLink Copied!Flag as InappropriateFlag as InappropriateHelpful 1 FCA Fiat Chrysler Automobiles Response seconds ago Edit Delete FCA Fiat Chrysler Automobiles 2017-09-30 21:14 PDT"
get_sentiments(lexicon = "nrc")%>%
    count(sentiment, sort = TRUE)

Convert the text data to dataframe.

GlassdoorPages <- data_frame(page = seq(1, n),
                      text = c(FCA_html))
GlassdoorPages%>%head()

Now we have the letters, and can convert this to a tidy text format.

tidy_FCA <- GlassdoorPages %>%
    unnest_tokens(word, text) %>%
    add_count(page) %>%
    dplyr::rename(page_total = n)
#remove stop words
data("stop_words")
tidy_FCA <- tidy_FCA %>%
  anti_join(stop_words)
stop_word=data_frame(word=c("chrysler","fca","linklink","fiat","whatsappshar","auburn","twittershar"))
tidy_FCA <- tidy_FCA %>%
  anti_join(stop_words)
tidy_FCA%>%head()

Next, let’s implement the sentiment analysis.

FCA_sentiment <- tidy_FCA %>%
    inner_join(get_sentiments("nrc"))
FCA_sentiment%>%head()

Now we have all we need to see the relative changes in these sentiments over the years.

theme_set(theme_bw())
#Alternatively
#FCA_sentiment%>%group_by(page, page_total, sentiment)%>%count()
FCA_sentiment %>%
    count(page, page_total, sentiment) %>%
    filter(sentiment %in% c("positive", "negative", 
                            "joy", "trust","fear","sadness"))%>%
    mutate(sentiment = as.factor(sentiment)) %>%
    #ggplot(aes(page, n / page_total, fill = sentiment)) +
     ggplot(aes(page, n / sum(n), fill = sentiment)) +
    geom_area(position = "identity", alpha = 0.5) +
    labs(y = "Relative frequency", x = "Page",
         title = "Sentiment analysis of FCA Glassdoor Reviews",
         subtitle = "Using the nrc lexicon")+theme_bw()+
 scale_fill_manual(values=viridis_pal(option = "D")(6))+
   scale_y_continuous(labels = scales::percent)

FCA_sentiment %>%
    count(page, page_total, sentiment) %>%
  #  filter(sentiment %in% c("positive", "negative",  "joy", "trust","fear","sadness"))%>%
  mutate(sentiment = forcats::fct_lump(sentiment, 6))%>%
    #mutate(sentiment = as.factor(sentiment)) %>%
    ggplot(aes(page, n / page_total, fill = sentiment)) +
     #ggplot(aes(page, n / sum(n), fill = sentiment)) +
    geom_area(position = "identity", alpha = 0.5) +
    labs(y = "Relative frequency", x = "Page",
         title = "Sentiment analysis of FCA Glassdoor Reviews",
         subtitle = "Using the nrc lexicon")+theme_bw()+
 scale_fill_manual(values=viridis_pal(option = "A")(7))+
   scale_y_continuous(labels = scales::percent)

tidy_FCA %>%
  inner_join(get_sentiments("afinn")) %>%
  group_by(page) %>%
  summarize(average_sentiment = mean(score), words = n()) %>%
  #filter(words >= 10) %>%
  ggplot(aes(page, average_sentiment)) +
  geom_line() +
  geom_hline(color = "red", lty = 2, yintercept = 0) +
labs(y = "Average AFINN sentiment score", x = "Page",
         title = "Sentiment analysis of FCA Glassdoor Reviews",
         subtitle = "Using the affin lexicon")

FCA_sentiment %>%
    count(sentiment, word) %>%
    filter(sentiment %in% c("positive", "negative", 
                            "joy", "trust","fear","sadness")) %>%
    group_by(sentiment) %>%
    top_n(10) %>%
    ungroup %>%
    mutate(word = reorder(word, n)) %>%
   mutate(sentiment = as.factor(sentiment))  %>%
    ggplot(aes(word, n, fill = sentiment)) +
    geom_bar(alpha = 0.8, show.legend = FALSE,stat = "identity") +
    coord_flip() +
    scale_y_continuous(expand = c(0,0)) +
    facet_wrap(~sentiment, scales = "free") +
   labs(y = "Total number of occurrences", x = "",
         title = "Sentiment analysis of FCA Glassdoor Reviews",
         subtitle = "Using the nrc lexicon")+theme_bw()+
scale_fill_manual(values=viridis_pal(option = "D")(6))

 # # change text into italics
 #        theme(strip.text = element_text(face = "italic")) +
 #  # strip horizontal  axis labels
 #        theme(axis.title.x=element_blank()) +
 #        theme(axis.ticks.x=element_blank()) +
 #        theme(axis.text.x=element_blank())
   

Plot without viridis package

FCA_sentiment %>%
    count(page, page_total, sentiment) %>%
    filter(sentiment %in% c("positive", "negative", 
                            "joy", "trust","fear","sadness"))%>%
     mutate(sentiment = factor(sentiment, levels = c("negative",
                                                    "positive",
                                                    "joy", "trust","fear","sadness"))) %>%
    ggplot(aes(page, n / page_total, fill = sentiment)) +
    geom_area(position = "identity", alpha = 0.5) +
    labs(y = "Relative frequency", x = NULL,
         title = "Sentiment analysis of FCA Glassdoor Reviews",
         subtitle = "Using the nrc")+theme_bw()

Using bing Lexicon

FCA_sentiment <- tidy_FCA %>%
    inner_join(get_sentiments("bing"))
FCA_sentiment %>%
    count(page, page_total, sentiment)%>%
    mutate(sentiment = as.factor(sentiment))%>%
    ggplot(aes(page, n / page_total, fill = sentiment)) +
    geom_area(position = "identity", alpha = 0.5) +
    labs(y = "Relative frequency", x = "Page",
         title = "Sentiment analysis of FCA Glassdoor Reviews",
         subtitle = "Using the nrc")+theme_bw()+
# scale_fill_manual(values=viridis_pal(option = "plasma")(2))+
   scale_y_continuous(labels = scales::percent)

The negative and positive sentiments distribution is similar with the negative sentiments having a higher peak. The negative reviews is evenly distributed as like the positive reviews. Neither is clearly superior over the other.

 GlassdoorPages %>%
    unnest_tokens(word, text)%>%
  inner_join(get_sentiments("bing")) %>%
  count(word, sentiment, sort = TRUE)%>%
  spread(sentiment,n,fill=0)%>%
  mutate(sentiment = positive -negative)%>%
  ggplot(aes(x = sentiment)) +
geom_density(color = palette_light()[1], fill = palette_light()[1], alpha = 0.8) +
theme_tq()+xlim(c(-5,5))

FCA_sentiment %>%
    count(sentiment, word) %>%
    group_by(sentiment) %>%
    top_n(15) %>%
    ungroup %>%
    mutate(word = reorder(word, n)) %>%
   mutate(sentiment = as.factor(sentiment))  %>%
    ggplot(aes(word, n, fill = sentiment)) +
    geom_col(alpha = 0.8, show.legend = FALSE) +
    coord_flip() +
    scale_y_continuous(expand = c(0,0)) +
    facet_wrap(~sentiment,scales="free") +
   labs(y = "Total number of occurrences", x = "",
         title = "Sentiment analysis of FCA Glassdoor Reviews",
         subtitle = "Using the bing lexicon")+
#scale_fill_manual(values=viridis_pal(option = "D")(8))+
 scale_fill_viridis(end = 0.75, discrete=TRUE, direction = -1) +
        scale_x_discrete(expand=c(0.02,0)) +
        theme(strip.text=element_text(hjust=0)) +
  # change text into italics
        theme(strip.text = element_text(face = "italic")) +
  # strip horizontal  axis labels
        theme(axis.title.x=element_blank()) +
        theme(axis.ticks.x=element_blank()) +
        theme(axis.text.x=element_blank())+
  theme_minimal(base_size = 13)

bing_word_counts <-tidy_FCA %>%
  inner_join(get_sentiments("bing")) %>%
  count(word, sentiment, sort = TRUE) %>%
  ungroup()
bing_word_counts%>%spread(sentiment,n,fill = 0)%>%top_n(10)
bing_word_counts%>%spread(sentiment,n,fill = 0)%>%top_n(-10)%>%head(10)
bing_word_counts %>%
  filter(n > 3) %>%
  mutate(n = if_else(sentiment == "negative", -n, n)) %>%
  mutate(word = reorder(word, n)) %>%
  ggplot(aes(word, n, fill = sentiment)) +
  geom_bar(stat = "identity") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
  ylab("Contribution to sentiment")+
#scale_fill_manual(values=viridis_pal(option = "D")(2))
 scale_fill_viridis(end = 0.85, discrete=TRUE, direction = 1)

Alternative waay of scrapping from several web pages


# n=4
# 
# FCA=list()
# 
# for (i in 2:n){
#   
# FCA[[1]]=read_html("https://www.glassdoor.com/Reviews/FCA-Fiat-Chrysler-Automobiles-Reviews-E149.htm")%>% html_nodes(".hreview")%>%html_text(trim = TRUE)
#   
# FCA[[i]]=read_html(paste("https://www.glassdoor.com/Reviews/FCA-Fiat-Chrysler-Automobiles-Reviews-E149_P",i,".htm",sep = ""))%>% html_nodes(".hreview")%>%html_text(trim = TRUE)
#   
# }

We see negative sentiment spiking, higher than positive sentiment, during the financial upheaval of 2008, the collapse of the dot-com bubble in the early 2000s, and the recession of the 1990s. Overall, though, notice that the balance of positive to negative sentiment is not as skewed to positive as when you use one of the general purpose sentiment lexicons.

This happens because of the words that are driving the sentiment score in these different cases. When using the financial sentiment lexicon, the words have specifically been chosen for a financial context. What words are driving these sentiment scores?

#FCA_html2<-FCA_html%>%str_replace_all("[[:xdigit:]]", "")
corpus = Corpus(VectorSource(FCA_html))
corpus = tm_map(corpus, tolower)
corpus<- tm_map(corpus, stripWhitespace)
corpus = tm_map(corpus, removeNumbers)
corpus = tm_map(corpus, removeWords, stopwords("english"))
stop_user=c("chrysler","fca","linklink","fiat","whatsappshar","auburn","twittershar","automobil","edit","delet","via","edit","delet","via","starstarstarstarstarwork","pdt","hill","facebookshar")
corpus = tm_map(corpus, removeWords,stop_user )
tdm <- TermDocumentMatrix(corpus,
                          control = list(removePunctuation = TRUE, 
                                      stopwords =  TRUE, 
                                      removeNumbers = TRUE, tolower = TRUE,
                                      PlainTextDocument=TRUE,
                                      stripWhitespace=TRUE, stemming = TRUE))
inspect(tdm)
<<TermDocumentMatrix (terms: 2144, documents: 155)>>
Non-/sparse entries: 9513/322807
Sparsity           : 97%
Maximal term length: 31
Weighting          : term frequency (tf)
Sample             :
           Docs
Terms       113 115 116 118 27 33 43 68 84 87
  ago         2   1   2   1  1  1  1  1  1  2
  automobil   2   2   2   2  3  2  2  2  3  3
  copi        1   1   1   1  1  1  1  1  1  1
  delet       1   1   1   1  1  1  1  1  1  1
  emailcopi   1   1   1   1  1  1  1  1  1  1
  employe     3   3   2   2  4  4  6  3  2  3
  life        1   1   3   1  2  1  1  1  1  2
  respons     1   1   1   1  1  1  1  1  1  1
  second      1   1   1   1  2  1  1  1  1  1
  work        4   1   2   1  8  0  3  2  3  3
tidy(tdm)
tdm = as.matrix(tdm)
frequencies = DocumentTermMatrix(corpus)
frequencies
<<DocumentTermMatrix (documents: 155, terms: 2640)>>
Non-/sparse entries: 10365/398835
Sparsity           : 97%
Maximal term length: 32
Weighting          : term frequency (tf)
findFreqTerms(frequencies, lowfreq=100)
 [1] "ago"                             "automobiles"                    
 [3] "copied"                          "delete"                         
 [5] "edit"                            "emailcopy"                      
 [7] "employee"                        "facebookshare"                  
 [9] "flag"                            "inappropriateflag"              
[11] "inappropriatehelpful"            "pdt"                            
[13] "response"                        "seconds"                        
[15] "time"                            "twittershare"                   
[17] "via"                             "whatsappshare"                  
[19] "work"                            "balanceculturevaluescareer"     
[21] "life"                            "opportunitiescompbenefitssenior"
[23] "starstarstarstarstarwork"        "anonymous"                      
#%>%as_tibble()%>%top_n(10)

Remove sparse terms

sparse = removeSparseTerms(frequencies, 0.995)
sparse
<<DocumentTermMatrix (documents: 155, terms: 2640)>>
Non-/sparse entries: 10365/398835
Sparsity           : 97%
Maximal term length: 32
Weighting          : term frequency (tf)

What about associations between words? Let’s have a look at what other words had a high association with “love”.

findAssocs(frequencies, c("love","poor","flexible","horrible"), c(0.6,0.6,0.6,0.6))
$love
       technician            dayton          featured       fieldconsno               ohi 
             0.69              0.63              0.63              0.63              0.63 
 retirementadvice            taught         workshare     yearsprospros         consworld 
             0.63              0.63              0.63              0.63              0.63 
dramaticallyshare          normally           related        scientific              test 
             0.63              0.63              0.63              0.63              0.63 

$poor
        seen       entire        since     advances      allowed     cheapest 
        0.67         0.67         0.64         0.63         0.63         0.63 
      choice         clue     creation      currect      decides    developed 
        0.63         0.63         0.63         0.63         0.63         0.63 
     drained      efforts       ethics     executed       exited  experiences 
        0.63         0.63         0.63         0.63         0.63         0.63 
   fantastic      feature        final     flounder      freedom        gouge 
        0.63         0.63         0.63         0.63         0.63         0.63 
        hair      hammers  highlighted  incorporate      initial     interest 
        0.63         0.63         0.63         0.63         0.63         0.63 
       items        labor      medical       merger      mirrors  outstanding 
        0.63         0.63         0.63         0.63         0.63         0.63 
 participate     partners   perception        price questionable       recent 
        0.63         0.63         0.63         0.63         0.63         0.63 
      reduce    requested        shown      sighted         sink         site 
        0.63         0.63         0.63         0.63         0.63         0.63 
     slogans        smoke    stimulate         swim       talked          tco 
        0.63         0.63         0.63         0.63         0.63         0.63 
   viability         view     warranty   washington     continue 
        0.63         0.63         0.63         0.63         0.62 

$flexible
numeric(0)

$horrible
   old office 
  0.62   0.60 
wc=Corpus(VectorSource(FCA_html[1:4]))
wc <- TermDocumentMatrix(wc)
wc=as.matrix(wc)
comparison.cloud(wc,scale=c(4,.5),max.words=300,
    random.order=FALSE,rot.per=.1,
    colors=palette_light()[1:4],
    use.r.layout=FALSE,title.size=3)

High Frequency Words

The most commonly words used in the reviews is plotted below.

v <-sort(rowSums(tdm),decreasing=TRUE)
d <-data_frame(word = names(v),freq=v) %>%mutate(word = reorder(word, freq))
head(d, 10)
wordcloud(words = d$word, freq = d$freq, min.freq = 3,
          max.words=200, random.order=FALSE, rot.per=0.35, 
          colors=brewer.pal(8, "Dark2"))

d=d[1:20,]
ggplot(d, aes(x=word,y=freq,fill="")) + 
 geom_bar(stat="identity")+theme_bw()+
  theme(axis.text.x =element_text(angle =45,hjust = 1))+
#scale_fill_viridis(end = 0.85, discrete=TRUE, direction = 1)
#scale_fill_manual(values=viridis_pal(option = "D")(1))
scale_color_manual(values = palette_light()) +
scale_fill_manual(values = palette_light())+
  coord_flip()

Word Cloud with Bing Lexicon

 GlassdoorPages %>%
    unnest_tokens(word, text)%>%
  inner_join(get_sentiments("bing")) %>%
  count(word, sentiment, sort = TRUE) %>%
 reshape2::acast(word ~ sentiment, value.var = "n", fill = 0) %>%
  comparison.cloud(colors = viridis_pal(option = "D")(2),
                   max.words = 100)

Word Cloud with nrc Lexicon

GlassdoorPages %>%
    unnest_tokens(word, text)%>%
  inner_join(get_sentiments("nrc")) %>%
  count(word, sentiment, sort = TRUE)%>%
  spread( word,n,fill = 0)
GlassdoorPages %>%
    unnest_tokens(word, text)%>%
  inner_join(get_sentiments("nrc")) %>%
  count(word, sentiment, sort = TRUE)%>%
filter(sentiment %in% c("negative","positive","joy","sadness"))%>%
reshape2::acast(word ~ sentiment, value.var = "n", fill = 0)%>% 
comparison.cloud(colors = viridis_pal(option = "D")(4),
                  max.words = 200)

LS0tCnRpdGxlOiAiU2VudGltZW50IEFuYWx5c2lzIG9mIEZDQSBFbXBsb3llZSBSZXZpZXdzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKYXV0aG9yOiBOYW5hIEJvYXRlbmcKZGZfcHJpbnQ6IHBhZ2VkClRpbWU6ICdgciBTeXMudGltZSgpYCcKZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJUIgJWQsICVZJylgIgotLS0KCgoKCmBgYHtyIHNldHVwLGluY2x1ZGU9RkFMU0V9Cgprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICBvdXQud2lkdGggPSIxMDAlIiwKICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIGZpZy5hbGlnbiA9ICdkZWZhdWx0JywgCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgZmlnLmNhcCA9IkZpZy4gMzAiLCAKICAgICAgICAgICAgICAgICAgICAgIG91dC53aWR0aD0iMTAwJSIpCgpgYGAKCgoqKiBJbnRyb2R1Y3Rpb24gIFdlYiBTY3JhcGluZyAqKgpXZWIgc2NyYXBpbmcgaXMgYSB0ZWNobmlxdWUgZm9yIGNvbnZlcnRpbmcgdGhlIGRhdGEgcHJlc2VudCBpbiB1bnN0cnVjdHVyZWQgZm9ybWF0IChIVE1MIHRhZ3MpIG92ZXIgdGhlIHdlYiB0byB0aGUgc3RydWN0dXJlZCBmb3JtYXQgd2hpY2ggY2FuIGVhc2lseSBiZSBhY2Nlc3NlZCBhbmQgdXNlZC4KTW9zdCBvZiB0aGUgZGF0YSBhdmFpbGFibGUgb3ZlciB0aGUgd2ViIGlzIG5vdCByZWFkaWx5IGF2YWlsYWJsZS4gSXQgaXMgcHJlc2VudCBpbiBhbiB1bnN0cnVjdHVyZWQgZm9ybWF0IChIVE1MIGZvcm1hdCkgYW5kIGlzIG5vdCBkb3dubG9hZGFibGUuIFRoZXJlZm9yZSwgaXQgcmVxdWlyZXMga25vd2xlZGdlIGFuZCBleHBlcnRpc2UgdG8gdXNlIHRoaXMgZGF0YS4KCldlIGNhbiBsb2NhdGUgdXNlZnVsIGRhdGEgYmFzZWQgb24gdGhlaXIgQ1NTIHNlbGVjdG9ycywgZXNwZWNpYWxseSB3aGVuIHRoZSB3ZWJwYWdlIHVzZXMgc2VtYW50aWMgdGFnIGF0dHJpYnV0ZXMuIFdlIGNhbiB1c2UgW3NlbGVjdG9yZ2FkZ2V0XSAoaHR0cDovL3NlbGVjdG9yZ2FkZ2V0LmNvbS8pIHRvIGZpbmQgb3V0IHdoaWNoIGNzcyBzZWxlY3RvciBtYXRjaGVzIHRoZSAicmV2aWV3Ii4gU2VsZWN0R2FkZ2V0IGNhbiBiZSBhZGRlZCBhbiBleHRlbnNpb24gaW4gIEdvb2dsZSBjaHJvbWUuIEl0IGlzIHNob3duIGFzIGEgbWFnbmlmeWluZyBnbGFzcy4KCmBgYHtyfQpwYWNtYW46OnBfbG9hZCh0aWR5dmVyc2UsdGlkeXRleHQsdmlyaWRpcyxydmVzdCx0bSx3b3JkY2xvdWQsU25vd2JhbGxDLHRpZHlxdWFudCkKYGBgCgoKCldlIGNhbiBzcGVjaWZ5IHRoZSBjc3Mgc2VsZWN0b3IgaW4gaHRtbF9ub2RlcygpIGFuZCBleHRyYWN0IHRoZSB0ZXh0IHdpdGggaHRtbF90ZXh0KCkuIFdlIHNjcmFiIG92ZXIgMTgwMCByZXZpZXdzIG9mIEZpYXQgQ2hyeXNsZXIgQXV0b21vYmlsZXMgZnJvbSBnbGFzc2Rvb3IuIFRoZXJlIGFyZSBhYm91dCAxNTUgd2VicGFnZXMgd2hpY2ggY29udGFpbiB0aGVzZSByZXZpZXdzLgoKCgoKCgpgYGB7cn0Kbj0xNTUKCiNUaGUgcmV2aWV3cyBoYXMgMTU1IHBhZ2VzLHRodXMgbj0xNTUKCkZDQV91cmxzIDwtIHBhc3RlMCgiaHR0cHM6Ly93d3cuZ2xhc3Nkb29yLmNvbS9SZXZpZXdzL0ZDQS1GaWF0LUNocnlzbGVyLUF1dG9tb2JpbGVzLVJldmlld3MtRTE0OV9QIixzZXEoMiwgbiksICIuaHRtIikKRkNBX3VybHM8LWMoImh0dHBzOi8vd3d3LmdsYXNzZG9vci5jb20vUmV2aWV3cy9GQ0EtRmlhdC1DaHJ5c2xlci1BdXRvbW9iaWxlcy1SZXZpZXdzLUUxNDkuaHRtIixGQ0FfdXJscykKCgpGQ0FfaHRtbCA8LSBGQ0FfdXJscyAlPiUKICAgIG1hcF9jaHIofiByZWFkX2h0bWwoLikgJT4lIGh0bWxfbm9kZSgiLmhyZXZpZXciKSU+JWh0bWxfdGV4dCgpKQoKRkNBX2h0bWxbWzFdXQoKYGBgCgojIyMgRGF0YSBQcmVwYXJhdGlvbgpXZSBjYW4gcmVtb3ZlIGFsbCB1bndhbnRlZCBjaGFyYWN0ZXJzIGF0IHRoaXMgc3RhZ2UKCmBgYHtyfQojRGF0YS1QcmVwcm9jZXNzaW5nOiByZW1vdmluZyAnXG4nCkZDQV9odG1sPC1nc3ViKCJcbiIsIiIsRkNBX2h0bWwpCgoKI3JlbW92ZSBhbGwgcm91bmQgYnJhY2tldHMKRkNBX2h0bWw8LUZDQV9odG1sJT4lc3RyX3JlcGxhY2VfYWxsKCJcXCh8XFwpIiwgIiIpCgojcmVtb3ZlIGFsbCBcXApGQ0FfaHRtbDwtRkNBX2h0bWwlPiVzdHJfcmVwbGFjZV9hbGwoIlxcXFwiLCAiIikKCgojcmVtb3ZlIGFsbCBub24gd29yZHMgYW5kIG5vbiBudW1iZXJzCgojRkNBX2h0bWw8LUZDQV9odG1sJT4lc3RyX3JlcGxhY2VfYWxsKCJbXkEtWmEtejAtOV0iLCAiIikKCiNyZW1vdmUgYWxsIOKAoiAKRkNBX2h0bWw8LUZDQV9odG1sJT4lc3RyX3JlcGxhY2VfYWxsKCJcXOKAoiAgIiwgIiIpCgojcmVtb3ZlIGFsbCAmIApGQ0FfaHRtbDwtRkNBX2h0bWwlPiVzdHJfcmVwbGFjZV9hbGwoIlxcICYgIiwgIiIpCgojcmVtb3ZlIGFsbCAgbm9uIHByaW50YWJsZSB3b3JkcwpGQ0FfaHRtbDwtRkNBX2h0bWwlPiVzdHJfcmVwbGFjZV9hbGwoIlteWzpwcmludDpdXSIsICIiKQoKRkNBX2h0bWw8LUZDQV9odG1sJT4lZ3N1YihwYXR0ZXJuID0gIlxcIC8iLCByZXBsYWNlbWVudCA9ICIiKQoKI0ZDQWluZGVlZDI8LUZDQWluZGVlZDIlPiVzdHJpbmdpOjpzdHJpX3VuZXNjYXBlX3VuaWNvZGUoKQoKRkNBX2h0bWxbWzFdXQoKYGBgCgoKCmBgYHtyfQpnZXRfc2VudGltZW50cyhsZXhpY29uID0gIm5yYyIpJT4lCiAgICBjb3VudChzZW50aW1lbnQsIHNvcnQgPSBUUlVFKQpgYGAKCgpDb252ZXJ0IHRoZSAgdGV4dCBkYXRhIHRvIGRhdGFmcmFtZS4KCmBgYHtyfQpHbGFzc2Rvb3JQYWdlcyA8LSBkYXRhX2ZyYW1lKHBhZ2UgPSBzZXEoMSwgbiksCiAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gYyhGQ0FfaHRtbCkpCgpHbGFzc2Rvb3JQYWdlcyU+JWhlYWQoKQpgYGAKCgoKTm93IHdlIGhhdmUgdGhlIGxldHRlcnMsIGFuZCBjYW4gY29udmVydCB0aGlzIHRvIGEgdGlkeSB0ZXh0IGZvcm1hdC4KCmBgYHtyfQoKCnRpZHlfRkNBIDwtIEdsYXNzZG9vclBhZ2VzICU+JQogICAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KSAlPiUKICAgIGFkZF9jb3VudChwYWdlKSAlPiUKICAgIGRwbHlyOjpyZW5hbWUocGFnZV90b3RhbCA9IG4pCgoKCiNyZW1vdmUgc3RvcCB3b3JkcwoKZGF0YSgic3RvcF93b3JkcyIpCnRpZHlfRkNBIDwtIHRpZHlfRkNBICU+JQogIGFudGlfam9pbihzdG9wX3dvcmRzKQoKCgoKYGBgCgoKYGBge3J9CnN0b3Bfd29yZD1kYXRhX2ZyYW1lKHdvcmQ9YygiY2hyeXNsZXIiLCJmY2EiLCJsaW5rbGluayIsImZpYXQiLCJ3aGF0c2FwcHNoYXIiLCJhdWJ1cm4iLCJ0d2l0dGVyc2hhciIpKQp0aWR5X0ZDQSA8LSB0aWR5X0ZDQSAlPiUKICBhbnRpX2pvaW4oc3RvcF93b3JkcykKCgp0aWR5X0ZDQSU+JWhlYWQoKQoKYGBgCgoKCgoKTmV4dCwgbGV04oCZcyBpbXBsZW1lbnQgdGhlIHNlbnRpbWVudCBhbmFseXNpcy4KCgpgYGB7cn0KRkNBX3NlbnRpbWVudCA8LSB0aWR5X0ZDQSAlPiUKICAgIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoIm5yYyIpKQoKRkNBX3NlbnRpbWVudCU+JWhlYWQoKQpgYGAKCk5vdyB3ZSBoYXZlIGFsbCB3ZSBuZWVkIHRvIHNlZSB0aGUgcmVsYXRpdmUgY2hhbmdlcyBpbiB0aGVzZSBzZW50aW1lbnRzIG92ZXIgdGhlIHllYXJzLgoKYGBge3J9CnRoZW1lX3NldCh0aGVtZV9idygpKQoKI0FsdGVybmF0aXZlbHkKI0ZDQV9zZW50aW1lbnQlPiVncm91cF9ieShwYWdlLCBwYWdlX3RvdGFsLCBzZW50aW1lbnQpJT4lY291bnQoKQoKRkNBX3NlbnRpbWVudCAlPiUKICAgIGNvdW50KHBhZ2UsIHBhZ2VfdG90YWwsIHNlbnRpbWVudCkgJT4lCiAgICBmaWx0ZXIoc2VudGltZW50ICVpbiUgYygicG9zaXRpdmUiLCAibmVnYXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJqb3kiLCAidHJ1c3QiLCJmZWFyIiwic2FkbmVzcyIpKSU+JQogICAgbXV0YXRlKHNlbnRpbWVudCA9IGFzLmZhY3RvcihzZW50aW1lbnQpKSAlPiUKICAgICNnZ3Bsb3QoYWVzKHBhZ2UsIG4gLyBwYWdlX3RvdGFsLCBmaWxsID0gc2VudGltZW50KSkgKwogICAgIGdncGxvdChhZXMocGFnZSwgbiAvIHN1bShuKSwgZmlsbCA9IHNlbnRpbWVudCkpICsKICAgIGdlb21fYXJlYShwb3NpdGlvbiA9ICJpZGVudGl0eSIsIGFscGhhID0gMC41KSArCiAgICBsYWJzKHkgPSAiUmVsYXRpdmUgZnJlcXVlbmN5IiwgeCA9ICJQYWdlIiwKICAgICAgICAgdGl0bGUgPSAiU2VudGltZW50IGFuYWx5c2lzIG9mIEZDQSBHbGFzc2Rvb3IgUmV2aWV3cyIsCiAgICAgICAgIHN1YnRpdGxlID0gIlVzaW5nIHRoZSBucmMgbGV4aWNvbiIpK3RoZW1lX2J3KCkrCiBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9dmlyaWRpc19wYWwob3B0aW9uID0gIkQiKSg2KSkrCiAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpCgoKCmBgYAoKCgpgYGB7cn0KRkNBX3NlbnRpbWVudCAlPiUKICAgIGNvdW50KHBhZ2UsIHBhZ2VfdG90YWwsIHNlbnRpbWVudCkgJT4lCiAgIyAgZmlsdGVyKHNlbnRpbWVudCAlaW4lIGMoInBvc2l0aXZlIiwgIm5lZ2F0aXZlIiwgICJqb3kiLCAidHJ1c3QiLCJmZWFyIiwic2FkbmVzcyIpKSU+JQogIG11dGF0ZShzZW50aW1lbnQgPSBmb3JjYXRzOjpmY3RfbHVtcChzZW50aW1lbnQsIDYpKSU+JQogICAgI211dGF0ZShzZW50aW1lbnQgPSBhcy5mYWN0b3Ioc2VudGltZW50KSkgJT4lCiAgICBnZ3Bsb3QoYWVzKHBhZ2UsIG4gLyBwYWdlX3RvdGFsLCBmaWxsID0gc2VudGltZW50KSkgKwogICAgICNnZ3Bsb3QoYWVzKHBhZ2UsIG4gLyBzdW0obiksIGZpbGwgPSBzZW50aW1lbnQpKSArCiAgICBnZW9tX2FyZWEocG9zaXRpb24gPSAiaWRlbnRpdHkiLCBhbHBoYSA9IDAuNSkgKwogICAgbGFicyh5ID0gIlJlbGF0aXZlIGZyZXF1ZW5jeSIsIHggPSAiUGFnZSIsCiAgICAgICAgIHRpdGxlID0gIlNlbnRpbWVudCBhbmFseXNpcyBvZiBGQ0EgR2xhc3Nkb29yIFJldmlld3MiLAogICAgICAgICBzdWJ0aXRsZSA9ICJVc2luZyB0aGUgbnJjIGxleGljb24iKSt0aGVtZV9idygpKwogc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPXZpcmlkaXNfcGFsKG9wdGlvbiA9ICJBIikoNykpKwogICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KQoKCmBgYAoKCgoKYGBge3J9CnRpZHlfRkNBICU+JQogIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImFmaW5uIikpICU+JQogIGdyb3VwX2J5KHBhZ2UpICU+JQogIHN1bW1hcml6ZShhdmVyYWdlX3NlbnRpbWVudCA9IG1lYW4oc2NvcmUpLCB3b3JkcyA9IG4oKSkgJT4lCiAgI2ZpbHRlcih3b3JkcyA+PSAxMCkgJT4lCiAgZ2dwbG90KGFlcyhwYWdlLCBhdmVyYWdlX3NlbnRpbWVudCkpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9obGluZShjb2xvciA9ICJyZWQiLCBsdHkgPSAyLCB5aW50ZXJjZXB0ID0gMCkgKwpsYWJzKHkgPSAiQXZlcmFnZSBBRklOTiBzZW50aW1lbnQgc2NvcmUiLCB4ID0gIlBhZ2UiLAogICAgICAgICB0aXRsZSA9ICJTZW50aW1lbnQgYW5hbHlzaXMgb2YgRkNBIEdsYXNzZG9vciBSZXZpZXdzIiwKICAgICAgICAgc3VidGl0bGUgPSAiVXNpbmcgdGhlIGFmZmluIGxleGljb24iKQpgYGAKCgoKYGBge3J9CkZDQV9zZW50aW1lbnQgJT4lCiAgICBjb3VudChzZW50aW1lbnQsIHdvcmQpICU+JQogICAgZmlsdGVyKHNlbnRpbWVudCAlaW4lIGMoInBvc2l0aXZlIiwgIm5lZ2F0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiam95IiwgInRydXN0IiwiZmVhciIsInNhZG5lc3MiKSkgJT4lCiAgICBncm91cF9ieShzZW50aW1lbnQpICU+JQogICAgdG9wX24oMTApICU+JQogICAgdW5ncm91cCAlPiUKICAgIG11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBuKSkgJT4lCiAgIG11dGF0ZShzZW50aW1lbnQgPSBhcy5mYWN0b3Ioc2VudGltZW50KSkgICU+JQogICAgZ2dwbG90KGFlcyh3b3JkLCBuLCBmaWxsID0gc2VudGltZW50KSkgKwogICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHNob3cubGVnZW5kID0gRkFMU0Usc3RhdCA9ICJpZGVudGl0eSIpICsKICAgIGNvb3JkX2ZsaXAoKSArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApKSArCiAgICBmYWNldF93cmFwKH5zZW50aW1lbnQsIHNjYWxlcyA9ICJmcmVlIikgKwogICBsYWJzKHkgPSAiVG90YWwgbnVtYmVyIG9mIG9jY3VycmVuY2VzIiwgeCA9ICIiLAogICAgICAgICB0aXRsZSA9ICJTZW50aW1lbnQgYW5hbHlzaXMgb2YgRkNBIEdsYXNzZG9vciBSZXZpZXdzIiwKICAgICAgICAgc3VidGl0bGUgPSAiVXNpbmcgdGhlIG5yYyBsZXhpY29uIikrdGhlbWVfYncoKSsKc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPXZpcmlkaXNfcGFsKG9wdGlvbiA9ICJEIikoNikpCgogIyAjIGNoYW5nZSB0ZXh0IGludG8gaXRhbGljcwogIyAgICAgICAgdGhlbWUoc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChmYWNlID0gIml0YWxpYyIpKSArCiAjICAjIHN0cmlwIGhvcml6b250YWwgIGF4aXMgbGFiZWxzCiAjICAgICAgICB0aGVtZShheGlzLnRpdGxlLng9ZWxlbWVudF9ibGFuaygpKSArCiAjICAgICAgICB0aGVtZShheGlzLnRpY2tzLng9ZWxlbWVudF9ibGFuaygpKSArCiAjICAgICAgICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCkpCiAgIApgYGAKCgoKIyMjIFBsb3Qgd2l0aG91dCB2aXJpZGlzIHBhY2thZ2UKCmBgYHtyfQoKCgoKRkNBX3NlbnRpbWVudCAlPiUKICAgIGNvdW50KHBhZ2UsIHBhZ2VfdG90YWwsIHNlbnRpbWVudCkgJT4lCiAgICBmaWx0ZXIoc2VudGltZW50ICVpbiUgYygicG9zaXRpdmUiLCAibmVnYXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJqb3kiLCAidHJ1c3QiLCJmZWFyIiwic2FkbmVzcyIpKSU+JQogICAgIG11dGF0ZShzZW50aW1lbnQgPSBmYWN0b3Ioc2VudGltZW50LCBsZXZlbHMgPSBjKCJuZWdhdGl2ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAicG9zaXRpdmUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImpveSIsICJ0cnVzdCIsImZlYXIiLCJzYWRuZXNzIikpKSAlPiUKICAgIGdncGxvdChhZXMocGFnZSwgbiAvIHBhZ2VfdG90YWwsIGZpbGwgPSBzZW50aW1lbnQpKSArCiAgICBnZW9tX2FyZWEocG9zaXRpb24gPSAiaWRlbnRpdHkiLCBhbHBoYSA9IDAuNSkgKwogICAgbGFicyh5ID0gIlJlbGF0aXZlIGZyZXF1ZW5jeSIsIHggPSBOVUxMLAogICAgICAgICB0aXRsZSA9ICJTZW50aW1lbnQgYW5hbHlzaXMgb2YgRkNBIEdsYXNzZG9vciBSZXZpZXdzIiwKICAgICAgICAgc3VidGl0bGUgPSAiVXNpbmcgdGhlIG5yYyIpK3RoZW1lX2J3KCkKCmBgYAoKIyMjIyBVc2luZyBiaW5nIExleGljb24KCmBgYHtyfQpGQ0Ffc2VudGltZW50IDwtIHRpZHlfRkNBICU+JQogICAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKQoKCkZDQV9zZW50aW1lbnQgJT4lCiAgICBjb3VudChwYWdlLCBwYWdlX3RvdGFsLCBzZW50aW1lbnQpJT4lCiAgICBtdXRhdGUoc2VudGltZW50ID0gYXMuZmFjdG9yKHNlbnRpbWVudCkpJT4lCiAgICBnZ3Bsb3QoYWVzKHBhZ2UsIG4gLyBwYWdlX3RvdGFsLCBmaWxsID0gc2VudGltZW50KSkgKwogICAgZ2VvbV9hcmVhKHBvc2l0aW9uID0gImlkZW50aXR5IiwgYWxwaGEgPSAwLjUpICsKICAgIGxhYnMoeSA9ICJSZWxhdGl2ZSBmcmVxdWVuY3kiLCB4ID0gIlBhZ2UiLAogICAgICAgICB0aXRsZSA9ICJTZW50aW1lbnQgYW5hbHlzaXMgb2YgRkNBIEdsYXNzZG9vciBSZXZpZXdzIiwKICAgICAgICAgc3VidGl0bGUgPSAiVXNpbmcgdGhlIG5yYyIpK3RoZW1lX2J3KCkrCiMgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPXZpcmlkaXNfcGFsKG9wdGlvbiA9ICJwbGFzbWEiKSgyKSkrCiAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpCmBgYAoKCgoKVGhlIG5lZ2F0aXZlIGFuZCBwb3NpdGl2ZSBzZW50aW1lbnRzIGRpc3RyaWJ1dGlvbiBpcyBzaW1pbGFyIHdpdGggdGhlIG5lZ2F0aXZlIHNlbnRpbWVudHMgaGF2aW5nIGEgaGlnaGVyIHBlYWsuIFRoZSBuZWdhdGl2ZSByZXZpZXdzIGlzIGV2ZW5seSBkaXN0cmlidXRlZCBhcyBsaWtlIHRoZSBwb3NpdGl2ZSByZXZpZXdzLgpOZWl0aGVyICBpcyBjbGVhcmx5ICBzdXBlcmlvciBvdmVyIHRoZSBvdGhlci4KCmBgYHtyfQogR2xhc3Nkb29yUGFnZXMgJT4lCiAgICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpJT4lCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUKICBjb3VudCh3b3JkLCBzZW50aW1lbnQsIHNvcnQgPSBUUlVFKSU+JQogIHNwcmVhZChzZW50aW1lbnQsbixmaWxsPTApJT4lCiAgbXV0YXRlKHNlbnRpbWVudCA9IHBvc2l0aXZlIC1uZWdhdGl2ZSklPiUKICBnZ3Bsb3QoYWVzKHggPSBzZW50aW1lbnQpKSArCmdlb21fZGVuc2l0eShjb2xvciA9IHBhbGV0dGVfbGlnaHQoKVsxXSwgZmlsbCA9IHBhbGV0dGVfbGlnaHQoKVsxXSwgYWxwaGEgPSAwLjgpICsKdGhlbWVfdHEoKSt4bGltKGMoLTUsNSkpCmBgYAoKCgpgYGB7cn0KRkNBX3NlbnRpbWVudCAlPiUKICAgIGNvdW50KHNlbnRpbWVudCwgd29yZCkgJT4lCiAgICBncm91cF9ieShzZW50aW1lbnQpICU+JQogICAgdG9wX24oMTUpICU+JQogICAgdW5ncm91cCAlPiUKICAgIG11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBuKSkgJT4lCiAgIG11dGF0ZShzZW50aW1lbnQgPSBhcy5mYWN0b3Ioc2VudGltZW50KSkgICU+JQogICAgZ2dwbG90KGFlcyh3b3JkLCBuLCBmaWxsID0gc2VudGltZW50KSkgKwogICAgZ2VvbV9jb2woYWxwaGEgPSAwLjgsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgIGNvb3JkX2ZsaXAoKSArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApKSArCiAgICBmYWNldF93cmFwKH5zZW50aW1lbnQsc2NhbGVzPSJmcmVlIikgKwogICBsYWJzKHkgPSAiVG90YWwgbnVtYmVyIG9mIG9jY3VycmVuY2VzIiwgeCA9ICIiLAogICAgICAgICB0aXRsZSA9ICJTZW50aW1lbnQgYW5hbHlzaXMgb2YgRkNBIEdsYXNzZG9vciBSZXZpZXdzIiwKICAgICAgICAgc3VidGl0bGUgPSAiVXNpbmcgdGhlIGJpbmcgbGV4aWNvbiIpKwojc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPXZpcmlkaXNfcGFsKG9wdGlvbiA9ICJEIikoOCkpKwogc2NhbGVfZmlsbF92aXJpZGlzKGVuZCA9IDAuNzUsIGRpc2NyZXRlPVRSVUUsIGRpcmVjdGlvbiA9IC0xKSArCiAgICAgICAgc2NhbGVfeF9kaXNjcmV0ZShleHBhbmQ9YygwLjAyLDApKSArCiAgICAgICAgdGhlbWUoc3RyaXAudGV4dD1lbGVtZW50X3RleHQoaGp1c3Q9MCkpICsKICAjIGNoYW5nZSB0ZXh0IGludG8gaXRhbGljcwogICAgICAgIHRoZW1lKHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJpdGFsaWMiKSkgKwogICMgc3RyaXAgaG9yaXpvbnRhbCAgYXhpcyBsYWJlbHMKICAgICAgICB0aGVtZShheGlzLnRpdGxlLng9ZWxlbWVudF9ibGFuaygpKSArCiAgICAgICAgdGhlbWUoYXhpcy50aWNrcy54PWVsZW1lbnRfYmxhbmsoKSkgKwogICAgICAgIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSkrCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMykKYGBgCgoKYGBge3J9CmJpbmdfd29yZF9jb3VudHMgPC10aWR5X0ZDQSAlPiUKICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJiaW5nIikpICU+JQogIGNvdW50KHdvcmQsIHNlbnRpbWVudCwgc29ydCA9IFRSVUUpICU+JQogIHVuZ3JvdXAoKQoKYmluZ193b3JkX2NvdW50cyU+JXNwcmVhZChzZW50aW1lbnQsbixmaWxsID0gMCklPiV0b3BfbigxMCkKYmluZ193b3JkX2NvdW50cyU+JXNwcmVhZChzZW50aW1lbnQsbixmaWxsID0gMCklPiV0b3BfbigtMTApJT4laGVhZCgxMCkKCmJpbmdfd29yZF9jb3VudHMgJT4lCiAgZmlsdGVyKG4gPiAzKSAlPiUKICBtdXRhdGUobiA9IGlmX2Vsc2Uoc2VudGltZW50ID09ICJuZWdhdGl2ZSIsIC1uLCBuKSkgJT4lCiAgbXV0YXRlKHdvcmQgPSByZW9yZGVyKHdvcmQsIG4pKSAlPiUKICBnZ3Bsb3QoYWVzKHdvcmQsIG4sIGZpbGwgPSBzZW50aW1lbnQpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKSArCiAgeWxhYigiQ29udHJpYnV0aW9uIHRvIHNlbnRpbWVudCIpKwojc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPXZpcmlkaXNfcGFsKG9wdGlvbiA9ICJEIikoMikpCiBzY2FsZV9maWxsX3ZpcmlkaXMoZW5kID0gMC44NSwgZGlzY3JldGU9VFJVRSwgZGlyZWN0aW9uID0gMSkKCmBgYAoKCiNBbHRlcm5hdGl2ZSB3YWF5IG9mIHNjcmFwcGluZyBmcm9tIHNldmVyYWwgd2ViIHBhZ2VzCgpgYGB7cn0KCiMgbj00CiMgCiMgRkNBPWxpc3QoKQojIAojIGZvciAoaSBpbiAyOm4pewojICAgCiMgRkNBW1sxXV09cmVhZF9odG1sKCJodHRwczovL3d3dy5nbGFzc2Rvb3IuY29tL1Jldmlld3MvRkNBLUZpYXQtQ2hyeXNsZXItQXV0b21vYmlsZXMtUmV2aWV3cy1FMTQ5Lmh0bSIpJT4lIGh0bWxfbm9kZXMoIi5ocmV2aWV3IiklPiVodG1sX3RleHQodHJpbSA9IFRSVUUpCiMgICAKIyBGQ0FbW2ldXT1yZWFkX2h0bWwocGFzdGUoImh0dHBzOi8vd3d3LmdsYXNzZG9vci5jb20vUmV2aWV3cy9GQ0EtRmlhdC1DaHJ5c2xlci1BdXRvbW9iaWxlcy1SZXZpZXdzLUUxNDlfUCIsaSwiLmh0bSIsc2VwID0gIiIpKSU+JSBodG1sX25vZGVzKCIuaHJldmlldyIpJT4laHRtbF90ZXh0KHRyaW0gPSBUUlVFKQojICAgCiMgfQoKCgoKCgpgYGAKCgoKCgoKCgoKCgoKCgoKCgoKCgoKV2Ugc2VlIG5lZ2F0aXZlIHNlbnRpbWVudCBzcGlraW5nLCBoaWdoZXIgdGhhbiBwb3NpdGl2ZSBzZW50aW1lbnQsIGR1cmluZyB0aGUgZmluYW5jaWFsIHVwaGVhdmFsIG9mIDIwMDgsIHRoZSBjb2xsYXBzZSBvZiB0aGUgZG90LWNvbSBidWJibGUgaW4gdGhlIGVhcmx5IDIwMDBzLCBhbmQgdGhlIHJlY2Vzc2lvbiBvZiB0aGUgMTk5MHMuIE92ZXJhbGwsIHRob3VnaCwgbm90aWNlIHRoYXQgdGhlIGJhbGFuY2Ugb2YgcG9zaXRpdmUgdG8gbmVnYXRpdmUgc2VudGltZW50IGlzIG5vdCBhcyBza2V3ZWQgdG8gcG9zaXRpdmUgYXMgd2hlbiB5b3UgdXNlIG9uZSBvZiB0aGUgZ2VuZXJhbCBwdXJwb3NlIHNlbnRpbWVudCBsZXhpY29ucy4KClRoaXMgaGFwcGVucyBiZWNhdXNlIG9mIHRoZSB3b3JkcyB0aGF0IGFyZSBkcml2aW5nIHRoZSBzZW50aW1lbnQgc2NvcmUgaW4gdGhlc2UgZGlmZmVyZW50IGNhc2VzLiBXaGVuIHVzaW5nIHRoZSBmaW5hbmNpYWwgc2VudGltZW50IGxleGljb24sIHRoZSB3b3JkcyBoYXZlIHNwZWNpZmljYWxseSBiZWVuIGNob3NlbiBmb3IgYSBmaW5hbmNpYWwgY29udGV4dC4gV2hhdCB3b3JkcyBhcmUgZHJpdmluZyB0aGVzZSBzZW50aW1lbnQgc2NvcmVzPwoKCgoKYGBge3J9CiNGQ0FfaHRtbDI8LUZDQV9odG1sJT4lc3RyX3JlcGxhY2VfYWxsKCJbWzp4ZGlnaXQ6XV0iLCAiIikKCmNvcnB1cyA9IENvcnB1cyhWZWN0b3JTb3VyY2UoRkNBX2h0bWwpKQoKY29ycHVzID0gdG1fbWFwKGNvcnB1cywgdG9sb3dlcikKY29ycHVzPC0gdG1fbWFwKGNvcnB1cywgc3RyaXBXaGl0ZXNwYWNlKQpjb3JwdXMgPSB0bV9tYXAoY29ycHVzLCByZW1vdmVOdW1iZXJzKQpjb3JwdXMgPSB0bV9tYXAoY29ycHVzLCByZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCJlbmdsaXNoIikpCnN0b3BfdXNlcj1jKCJjaHJ5c2xlciIsImZjYSIsImxpbmtsaW5rIiwiZmlhdCIsIndoYXRzYXBwc2hhciIsImF1YnVybiIsInR3aXR0ZXJzaGFyIiwiYXV0b21vYmlsIiwiZWRpdCIsImRlbGV0IiwidmlhIiwiZWRpdCIsImRlbGV0IiwidmlhIiwic3RhcnN0YXJzdGFyc3RhcnN0YXJ3b3JrIiwicGR0IiwiaGlsbCIsImZhY2Vib29rc2hhciIpCmNvcnB1cyA9IHRtX21hcChjb3JwdXMsIHJlbW92ZVdvcmRzLHN0b3BfdXNlciApCgoKdGRtIDwtIFRlcm1Eb2N1bWVudE1hdHJpeChjb3JwdXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbCA9IGxpc3QocmVtb3ZlUHVuY3R1YXRpb24gPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdG9wd29yZHMgPSAgVFJVRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVtb3ZlTnVtYmVycyA9IFRSVUUsIHRvbG93ZXIgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBsYWluVGV4dERvY3VtZW50PVRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaXBXaGl0ZXNwYWNlPVRSVUUsIHN0ZW1taW5nID0gVFJVRSkpCgoKCmluc3BlY3QodGRtKQoKCnRpZHkodGRtKQoKdGRtID0gYXMubWF0cml4KHRkbSkKCgoKYGBgCgoKCgpgYGB7cn0KZnJlcXVlbmNpZXMgPSBEb2N1bWVudFRlcm1NYXRyaXgoY29ycHVzKQpmcmVxdWVuY2llcwpgYGAKCgpgYGB7cn0KCmZpbmRGcmVxVGVybXMoZnJlcXVlbmNpZXMsIGxvd2ZyZXE9MTAwKQojJT4lYXNfdGliYmxlKCklPiV0b3BfbigxMCkKYGBgCgpSZW1vdmUgc3BhcnNlIHRlcm1zCmBgYHtyfQpzcGFyc2UgPSByZW1vdmVTcGFyc2VUZXJtcyhmcmVxdWVuY2llcywgMC45OTUpCnNwYXJzZQpgYGAKCldoYXQgYWJvdXQgYXNzb2NpYXRpb25zIGJldHdlZW4gd29yZHM/IExldOKAmXMgaGF2ZSBhIGxvb2sgYXQgd2hhdCBvdGhlciB3b3JkcyBoYWQgYSBoaWdoIGFzc29jaWF0aW9uIHdpdGgg4oCcbG92ZeKAnS4KYGBge3J9CgpmaW5kQXNzb2NzKGZyZXF1ZW5jaWVzLCBjKCJsb3ZlIiwicG9vciIsImZsZXhpYmxlIiwiaG9ycmlibGUiKSwgYygwLjYsMC42LDAuNiwwLjYpKQoKYGBgCgoKCgoKCmBgYHtyfQp3Yz1Db3JwdXMoVmVjdG9yU291cmNlKEZDQV9odG1sWzE6NF0pKQp3YyA8LSBUZXJtRG9jdW1lbnRNYXRyaXgod2MpCndjPWFzLm1hdHJpeCh3YykKY29tcGFyaXNvbi5jbG91ZCh3YyxzY2FsZT1jKDQsLjUpLG1heC53b3Jkcz0zMDAsCglyYW5kb20ub3JkZXI9RkFMU0Uscm90LnBlcj0uMSwKCWNvbG9ycz1wYWxldHRlX2xpZ2h0KClbMTo0XSwKCXVzZS5yLmxheW91dD1GQUxTRSx0aXRsZS5zaXplPTMpCgoKCmBgYAoKCgojIyMgSGlnaCBGcmVxdWVuY3kgV29yZHMKVGhlIG1vc3QgY29tbW9ubHkgd29yZHMgdXNlZCBpbiB0aGUgcmV2aWV3cyBpcyBwbG90dGVkIGJlbG93LgoKYGBge3J9CnYgPC1zb3J0KHJvd1N1bXModGRtKSxkZWNyZWFzaW5nPVRSVUUpCmQgPC1kYXRhX2ZyYW1lKHdvcmQgPSBuYW1lcyh2KSxmcmVxPXYpICU+JW11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBmcmVxKSkKaGVhZChkLCAxMCkKCndvcmRjbG91ZCh3b3JkcyA9IGQkd29yZCwgZnJlcSA9IGQkZnJlcSwgbWluLmZyZXEgPSAzLAogICAgICAgICAgbWF4LndvcmRzPTIwMCwgcmFuZG9tLm9yZGVyPUZBTFNFLCByb3QucGVyPTAuMzUsIAogICAgICAgICAgY29sb3JzPWJyZXdlci5wYWwoOCwgIkRhcmsyIikpCgpkPWRbMToyMCxdCmdncGxvdChkLCBhZXMoeD13b3JkLHk9ZnJlcSxmaWxsPSIiKSkgKyAKIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikrdGhlbWVfYncoKSsKICB0aGVtZShheGlzLnRleHQueCA9ZWxlbWVudF90ZXh0KGFuZ2xlID00NSxoanVzdCA9IDEpKSsKI3NjYWxlX2ZpbGxfdmlyaWRpcyhlbmQgPSAwLjg1LCBkaXNjcmV0ZT1UUlVFLCBkaXJlY3Rpb24gPSAxKQojc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPXZpcmlkaXNfcGFsKG9wdGlvbiA9ICJEIikoMSkpCnNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlX2xpZ2h0KCkpICsKc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZV9saWdodCgpKSsKICBjb29yZF9mbGlwKCkKYGBgCgoKIyMjIFdvcmQgQ2xvdWQgd2l0aCBCaW5nIExleGljb24KCmBgYHtyfQoKYygiZWRpdCIsImRlbGV0IiwidmlhIiwic3RhcnN0YXJzdGFyc3RhcnN0YXJ3b3JrIiwicGR0IiwiaGlsbCIsImZhY2Vib29rc2hhciIpCgogR2xhc3Nkb29yUGFnZXMgJT4lCiAgICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpJT4lCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUKICBjb3VudCh3b3JkLCBzZW50aW1lbnQsIHNvcnQgPSBUUlVFKSAlPiUKIHJlc2hhcGUyOjphY2FzdCh3b3JkIH4gc2VudGltZW50LCB2YWx1ZS52YXIgPSAibiIsIGZpbGwgPSAwKSAlPiUKICBjb21wYXJpc29uLmNsb3VkKGNvbG9ycyA9IHZpcmlkaXNfcGFsKG9wdGlvbiA9ICJEIikoMiksCiAgICAgICAgICAgICAgICAgICBtYXgud29yZHMgPSAxMDApCmBgYAoKCgoKCiMjIyBXb3JkIENsb3VkIHdpdGggbnJjIExleGljb24KCgpgYGB7cn0KR2xhc3Nkb29yUGFnZXMgJT4lCiAgICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpJT4lCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygibnJjIikpICU+JQogIGNvdW50KHdvcmQsIHNlbnRpbWVudCwgc29ydCA9IFRSVUUpJT4lCiAgc3ByZWFkKCB3b3JkLG4sZmlsbCA9IDApCmBgYAoKCgoKYGBge3J9CgpHbGFzc2Rvb3JQYWdlcyAlPiUKICAgIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCklPiUKICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJucmMiKSkgJT4lCiAgY291bnQod29yZCwgc2VudGltZW50LCBzb3J0ID0gVFJVRSklPiUKZmlsdGVyKHNlbnRpbWVudCAlaW4lIGMoIm5lZ2F0aXZlIiwicG9zaXRpdmUiLCJqb3kiLCJzYWRuZXNzIikpJT4lCnJlc2hhcGUyOjphY2FzdCh3b3JkIH4gc2VudGltZW50LCB2YWx1ZS52YXIgPSAibiIsIGZpbGwgPSAwKSU+JSAKY29tcGFyaXNvbi5jbG91ZChjb2xvcnMgPSB2aXJpZGlzX3BhbChvcHRpb24gPSAiRCIpKDQpLAogICAgICAgICAgICAgICAgICBtYXgud29yZHMgPSAyMDApCgoKCmBgYAoKCg==